Skip to content

fix: check target run capabilities before encrypting hook payloads#1572

Open
TooTallNate wants to merge 1 commit intomainfrom
fix/hook-resume-encryption-compat
Open

fix: check target run capabilities before encrypting hook payloads#1572
TooTallNate wants to merge 1 commit intomainfrom
fix/hook-resume-encryption-compat

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

Summary

  • Fixes resumeHook()/resumeWebhook() failing with Unknown serialization format: "encr" when the target workflow run was created by a pre-encryption deployment
  • Adds a capabilities table (packages/core/src/capabilities.ts) that maps @workflow/core versions to supported serialization formats, using the semver package for version comparison
  • Before encoding the hook payload, resumeHook() now checks the target run's workflowCoreVersion (from executionContext) and suppresses encryption when the run's deployment predates the encr format (introduced in 4.2.0-beta.64)

How it works

Three data formats exist in the wild:

Format Condition Behavior
v1 "living devalue" specVersion === 1 (legacy) Already handled by isLegacySpecVersion()
v2 devl binary specVersion === 2, workflowCoreVersion < 4.2.0-beta.64 NEW: encryption key suppressed via capabilities check
v2 encr + devl specVersion === 2, workflowCoreVersion >= 4.2.0-beta.64 Encrypted as before

When workflowCoreVersion is undefined (very old runs that predate the field), we assume the most conservative capabilities (no encryption).

Additional changes

  • Added semver to the pnpm workspace catalog and updated @workflow/next to use catalog: instead of a hardcoded version

@TooTallNate TooTallNate requested a review from a team as a code owner March 31, 2026 21:48
Copilot AI review requested due to automatic review settings March 31, 2026 21:48
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 31, 2026

🦋 Changeset detected

Latest commit: d251cd6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
@workflow/core Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
workflow Patch
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch
@workflow/ai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Mar 31, 2026 9:59pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Mar 31, 2026 9:59pm
example-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-astro-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-express-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-fastify-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-hono-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-nitro-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-nuxt-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workbench-vite-workflow Ready Ready Preview, Comment Mar 31, 2026 9:59pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Mar 31, 2026 9:59pm
workflow-swc-playground Ready Ready Preview, Comment Mar 31, 2026 9:59pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 857 0 67 924
✅ 💻 Local Development 830 0 178 1008
✅ 📦 Local Production 760 0 164 924
✅ 🐘 Local Postgres 830 0 178 1008
✅ 🪟 Windows 76 0 8 84
❌ 🌍 Community Worlds 133 59 24 216
✅ 📋 Other 210 0 42 252
Total 3696 59 661 4416

❌ Failed Tests

🌍 Community Worlds (59 failed)

mongodb (2 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KN2YDX81XDANVEDP24JRQ8P1
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KN2YNF55M7A21DT0EBSRE879

redis (2 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KN2YDX81XDANVEDP24JRQ8P1
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KN2YNF55M7A21DT0EBSRE879

turso (55 failed):

  • addTenWorkflow | wrun_01KN2YCV2VW0AR7CZ42017DPG3
  • addTenWorkflow | wrun_01KN2YCV2VW0AR7CZ42017DPG3
  • wellKnownAgentWorkflow (.well-known/agent) | wrun_01KN2YE62J81MW23FN9W78MPDN
  • should work with react rendering in step
  • promiseAllWorkflow | wrun_01KN2YD134SYSJ3XKT01HS44GV
  • promiseRaceWorkflow | wrun_01KN2YD56S2GRXNE3T79659YSW
  • promiseAnyWorkflow | wrun_01KN2YD7MZGP680EP5Z435G21N
  • importedStepOnlyWorkflow | wrun_01KN2YEK0D06X12HVBFS78X0QV
  • hookWorkflow | wrun_01KN2YDKF2Z9HB4FW0CXPG8P8M
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KN2YDX81XDANVEDP24JRQ8P1
  • webhookWorkflow | wrun_01KN2YE5HFBB3ZNS9P2F7EXTD8
  • sleepingWorkflow | wrun_01KN2YEBNCNKVFQ1KEX3CJ8241
  • parallelSleepWorkflow | wrun_01KN2YEQGA3D6RTFFV0J8WDN4A
  • nullByteWorkflow | wrun_01KN2YEVM3H417E7MB30AW3QHV
  • workflowAndStepMetadataWorkflow | wrun_01KN2YEXJ18FT6Q59DA32XSQ5X
  • fetchWorkflow | wrun_01KN2YHGCS3JCP0X643FDPTY00
  • promiseRaceStressTestWorkflow | wrun_01KN2YHKCZ1HGWM6X08QEE3Q7V
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • error handling not registered WorkflowNotRegisteredError fails the run when workflow does not exist
  • error handling not registered StepNotRegisteredError fails the step but workflow can catch it
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KN2YMVYGJWHRJCFP9034V1J1
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KN2YNF55M7A21DT0EBSRE879
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KN2YP1XFPVF1PFT10K59GXJ0
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KN2YPMVXTDFPPMWNXG0KJHAK
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KN2YPWJ4SM69Q0TYFHP2P9ZS
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KN2YQ17GZSFKM5BNBK2BJPCZ
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KN2YQ33MM0BF1ED8G76Q2N0J
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KN2YQGJB0PK88P93E2EKNHW9
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KN2YQN4N8TGX581FJCE8EYCH
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KN2YQVCVKYA9X9SVJWXMWG19
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KN2YR18PENF1TQ567PDK27Y5
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KN2YR72QKTS7N72RAM1NQRAC
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KN2YREN5A42N1T8H12EHSEPH
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KN2YRMQ50R4KB9XFS5HYRDD6
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KN2YRZFF3DW4QNDF2D4G1C5Z
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KN2YS6FR63CB2WS1EECZN195
  • cancelRun - cancelling a running workflow | wrun_01KN2YSC7EHT0AV46KD8R5K0RN
  • cancelRun via CLI - cancelling a running workflow | wrun_01KN2YSMN5W4HCT632PQQG43MD
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KN2YSZSHZWWV46Q8Z1Z9A4MR
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KN2YTK4NM1TJ4MTJH2RYFSC5
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KN2YTY06J72Y1KZ43S9KYW5W
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KN2YV4EXH0M9QQ8Y6K87MXRE

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 77 0 7
✅ example 77 0 7
✅ express 77 0 7
✅ fastify 77 0 7
✅ hono 77 0 7
✅ nextjs-turbopack 82 0 2
✅ nextjs-webpack 82 0 2
✅ nitro 77 0 7
✅ nuxt 77 0 7
✅ sveltekit 77 0 7
✅ vite 77 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 70 0 14
✅ express-stable 70 0 14
✅ fastify-stable 70 0 14
✅ hono-stable 70 0 14
✅ nextjs-turbopack-canary 59 0 25
✅ nextjs-turbopack-stable 76 0 8
✅ nextjs-webpack-canary 59 0 25
✅ nextjs-webpack-stable 76 0 8
✅ nitro-stable 70 0 14
✅ nuxt-stable 70 0 14
✅ sveltekit-stable 70 0 14
✅ vite-stable 70 0 14
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 70 0 14
✅ express-stable 70 0 14
✅ fastify-stable 70 0 14
✅ hono-stable 70 0 14
✅ nextjs-turbopack-canary 59 0 25
✅ nextjs-turbopack-stable 76 0 8
✅ nextjs-webpack-canary 59 0 25
✅ nextjs-webpack-stable 76 0 8
✅ nitro-stable 70 0 14
✅ sveltekit-stable 70 0 14
✅ vite-stable 70 0 14
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 70 0 14
✅ express-stable 70 0 14
✅ fastify-stable 70 0 14
✅ hono-stable 70 0 14
✅ nextjs-turbopack-canary 59 0 25
✅ nextjs-turbopack-stable 76 0 8
✅ nextjs-webpack-canary 59 0 25
✅ nextjs-webpack-stable 76 0 8
✅ nitro-stable 70 0 14
✅ nuxt-stable 70 0 14
✅ sveltekit-stable 70 0 14
✅ vite-stable 70 0 14
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 76 0 8
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 5 0 0
❌ mongodb 57 2 8
✅ redis-dev 5 0 0
❌ redis 57 2 8
✅ turso-dev 5 0 0
❌ turso 4 55 8
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 70 0 14
✅ e2e-local-postgres-nest-stable 70 0 14
✅ e2e-local-prod-nest-stable 70 0 14

📋 View full workflow run


Some E2E test jobs failed:

  • Vercel Prod: success
  • Local Dev: success
  • Local Prod: failure
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.044s (~) 1.005s (~) 0.960s 10 1.00x
💻 Local Express 0.045s (~) 1.005s (~) 0.960s 10 1.02x
🌐 Redis Next.js (Turbopack) 0.046s (+4.8%) 1.005s (~) 0.958s 10 1.04x
💻 Local Next.js (Turbopack) 0.049s (-6.3% 🟢) 1.006s (~) 0.957s 10 1.10x
🐘 Postgres Express 0.051s (-26.6% 🟢) 1.010s (~) 0.960s 10 1.14x
🐘 Postgres Next.js (Turbopack) 0.060s (-10.0% 🟢) 1.013s (~) 0.953s 10 1.36x
🐘 Postgres Nitro 0.068s (-3.0%) 1.014s (~) 0.947s 10 1.52x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.495s (-1.8%) 2.311s (-14.2% 🟢) 1.816s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.523s (-11.8% 🟢) 2.705s (+9.7% 🔺) 2.181s 10 1.06x
▲ Vercel Nitro 0.553s (+5.5% 🔺) 2.537s (-7.7% 🟢) 1.983s 10 1.12x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.104s (-0.7%) 2.006s (~) 0.902s 10 1.00x
💻 Local Next.js (Turbopack) 1.116s (~) 2.006s (~) 0.890s 10 1.01x
💻 Local Nitro 1.125s (-0.6%) 2.006s (~) 0.881s 10 1.02x
💻 Local Express 1.127s (~) 2.006s (~) 0.879s 10 1.02x
🐘 Postgres Express 1.130s (-1.8%) 2.013s (~) 0.883s 10 1.02x
🐘 Postgres Next.js (Turbopack) 1.145s (~) 2.011s (~) 0.865s 10 1.04x
🐘 Postgres Nitro 1.147s (~) 2.012s (~) 0.865s 10 1.04x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.123s (+2.0%) 3.736s (-4.1%) 1.613s 10 1.00x
▲ Vercel Next.js (Turbopack) 2.148s (+2.7%) 3.905s (~) 1.757s 10 1.01x
▲ Vercel Express 2.559s (+13.0% 🔺) 4.105s (+3.7%) 1.545s 10 1.21x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 10.595s (~) 11.023s (~) 0.427s 3 1.00x
🐘 Postgres Express 10.697s (-2.3%) 11.019s (~) 0.322s 3 1.01x
💻 Local Next.js (Turbopack) 10.785s (~) 11.024s (~) 0.239s 3 1.02x
🐘 Postgres Next.js (Turbopack) 10.840s (-0.8%) 11.028s (~) 0.188s 3 1.02x
🐘 Postgres Nitro 10.894s (~) 11.025s (~) 0.131s 3 1.03x
💻 Local Nitro 10.907s (~) 11.023s (~) 0.116s 3 1.03x
💻 Local Express 10.925s (~) 11.024s (~) 0.099s 3 1.03x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 17.230s (-1.8%) 18.529s (-5.7% 🟢) 1.298s 2 1.00x
▲ Vercel Nitro 17.584s (-23.7% 🟢) 19.152s (-24.7% 🟢) 1.568s 2 1.02x
▲ Vercel Next.js (Turbopack) 19.119s (+11.9% 🔺) 20.811s (+8.8% 🔺) 1.691s 2 1.11x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 13.843s (~) 14.027s (-1.4%) 0.183s 5 1.00x
🐘 Postgres Express 14.047s (-3.8%) 15.024s (~) 0.977s 4 1.01x
🐘 Postgres Next.js (Turbopack) 14.413s (~) 15.029s (~) 0.616s 4 1.04x
🐘 Postgres Nitro 14.578s (~) 15.029s (~) 0.451s 4 1.05x
💻 Local Next.js (Turbopack) 14.614s (~) 15.029s (~) 0.416s 4 1.06x
💻 Local Nitro 14.919s (~) 15.028s (~) 0.109s 4 1.08x
💻 Local Express 14.934s (~) 15.028s (~) 0.094s 4 1.08x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 32.897s (-6.0% 🟢) 35.007s (-4.9%) 2.110s 2 1.00x
▲ Vercel Nitro 42.214s (+31.3% 🔺) 44.814s (+31.3% 🔺) 2.601s 2 1.28x
▲ Vercel Next.js (Turbopack) 44.069s (+32.9% 🔺) 46.245s (+31.9% 🔺) 2.176s 2 1.34x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 12.642s (~) 13.024s (~) 0.382s 7 1.00x
🐘 Postgres Express 13.077s (-9.2% 🟢) 13.594s (-9.5% 🟢) 0.516s 7 1.03x
🐘 Postgres Next.js (Turbopack) 13.801s (-1.0%) 14.023s (-1.0%) 0.223s 7 1.09x
🐘 Postgres Nitro 13.998s (-0.7%) 14.454s (-1.9%) 0.456s 7 1.11x
💻 Local Next.js (Turbopack) 15.892s (-2.3%) 16.027s (-5.9% 🟢) 0.135s 6 1.26x
💻 Local Express 16.668s (~) 17.029s (~) 0.361s 6 1.32x
💻 Local Nitro 16.684s (+0.8%) 17.032s (~) 0.347s 6 1.32x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 60.493s (+5.1% 🔺) 62.373s (+4.0%) 1.879s 2 1.00x
▲ Vercel Express 65.840s (+17.2% 🔺) 67.177s (+15.5% 🔺) 1.337s 2 1.09x
▲ Vercel Next.js (Turbopack) 68.986s (+15.3% 🔺) 71.188s (+15.6% 🔺) 2.202s 2 1.14x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.212s (-4.9%) 2.011s (~) 0.800s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.240s (~) 2.011s (~) 0.770s 15 1.02x
🌐 Redis Next.js (Turbopack) 1.247s (+1.9%) 2.006s (~) 0.758s 15 1.03x
🐘 Postgres Nitro 1.263s (~) 2.013s (~) 0.750s 15 1.04x
💻 Local Nitro 1.489s (-2.9%) 2.005s (~) 0.517s 15 1.23x
💻 Local Express 1.507s (-2.1%) 2.005s (~) 0.498s 15 1.24x
💻 Local Next.js (Turbopack) 1.524s (-3.1%) 2.006s (~) 0.482s 15 1.26x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.459s (-5.1% 🟢) 3.777s (-15.8% 🟢) 1.318s 8 1.00x
▲ Vercel Next.js (Turbopack) 2.634s (+1.2%) 4.158s (+2.4%) 1.524s 8 1.07x
▲ Vercel Nitro 2.649s (+8.7% 🔺) 4.202s (+4.1%) 1.553s 8 1.08x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.284s (-3.5%) 3.010s (~) 0.726s 10 1.00x
🐘 Postgres Nitro 2.349s (~) 3.011s (~) 0.661s 10 1.03x
🐘 Postgres Next.js (Turbopack) 2.396s (-1.9%) 3.011s (~) 0.616s 10 1.05x
🌐 Redis Next.js (Turbopack) 2.425s (+0.8%) 3.008s (~) 0.583s 10 1.06x
💻 Local Next.js (Turbopack) 2.675s (-4.6%) 3.208s (-7.1% 🟢) 0.533s 10 1.17x
💻 Local Express 3.005s (+2.7%) 3.208s (+6.6% 🔺) 0.203s 10 1.32x
💻 Local Nitro 3.031s (+2.2%) 3.564s (+3.2%) 0.533s 9 1.33x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.757s (-24.1% 🟢) 4.209s (-17.4% 🟢) 1.451s 8 1.00x
▲ Vercel Express 3.071s (+16.8% 🔺) 4.726s (+8.3% 🔺) 1.655s 7 1.11x
▲ Vercel Nitro 3.402s (+19.5% 🔺) 5.007s (+17.4% 🔺) 1.606s 6 1.23x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.376s (-3.3%) 4.012s (~) 0.636s 8 1.00x
🐘 Postgres Nitro 3.487s (~) 4.014s (~) 0.526s 8 1.03x
🐘 Postgres Next.js (Turbopack) 3.644s (~) 4.013s (~) 0.369s 8 1.08x
🌐 Redis Next.js (Turbopack) 4.011s (+2.6%) 4.260s (+3.0%) 0.249s 8 1.19x
💻 Local Next.js (Turbopack) 7.574s (-3.1%) 8.266s (-3.0%) 0.692s 4 2.24x
💻 Local Express 8.625s (+5.7% 🔺) 9.023s (~) 0.398s 4 2.55x
💻 Local Nitro 9.199s (+14.5% 🔺) 10.025s (+17.6% 🔺) 0.827s 3 2.72x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.076s (+1.3%) 4.794s (-5.2% 🟢) 1.718s 7 1.00x
▲ Vercel Express 3.354s (-8.3% 🟢) 4.735s (-16.7% 🟢) 1.381s 8 1.09x
▲ Vercel Next.js (Turbopack) 3.833s (~) 5.600s (-4.3%) 1.767s 6 1.25x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.216s (-3.1%) 2.010s (~) 0.794s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.230s (~) 2.010s (~) 0.780s 15 1.01x
🐘 Postgres Nitro 1.252s (-1.3%) 2.010s (~) 0.758s 15 1.03x
🌐 Redis Next.js (Turbopack) 1.257s (+2.1%) 2.006s (~) 0.749s 15 1.03x
💻 Local Next.js (Turbopack) 1.470s (-2.3%) 2.006s (~) 0.536s 15 1.21x
💻 Local Express 1.553s (~) 2.006s (~) 0.452s 15 1.28x
💻 Local Nitro 1.576s (~) 2.006s (~) 0.430s 15 1.30x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.232s (-3.8%) 3.522s (-12.1% 🟢) 1.290s 9 1.00x
▲ Vercel Nitro 2.265s (-7.5% 🟢) 3.903s (-2.3%) 1.637s 8 1.02x
▲ Vercel Next.js (Turbopack) 2.437s (+2.9%) 4.092s (+2.6%) 1.656s 8 1.09x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.272s (-2.0%) 3.010s (~) 0.738s 10 1.00x
🐘 Postgres Nitro 2.326s (-1.3%) 3.009s (~) 0.684s 10 1.02x
🐘 Postgres Next.js (Turbopack) 2.388s (~) 3.010s (~) 0.622s 10 1.05x
🌐 Redis Next.js (Turbopack) 2.439s (+1.1%) 3.008s (~) 0.568s 10 1.07x
💻 Local Next.js (Turbopack) 2.921s (-3.7%) 3.453s (~) 0.531s 9 1.29x
💻 Local Nitro 3.032s (-0.5%) 3.760s (-3.2%) 0.728s 8 1.33x
💻 Local Express 3.114s (~) 3.760s (-3.3%) 0.645s 8 1.37x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.616s (-10.6% 🟢) 4.177s (-8.0% 🟢) 1.561s 8 1.00x
▲ Vercel Nitro 2.823s (-2.5%) 4.457s (+3.3%) 1.634s 7 1.08x
▲ Vercel Next.js (Turbopack) 3.229s (+3.7%) 4.921s (+5.2% 🔺) 1.692s 7 1.23x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.383s (-2.8%) 4.013s (~) 0.630s 8 1.00x
🐘 Postgres Nitro 3.501s (~) 4.011s (~) 0.510s 8 1.04x
🐘 Postgres Next.js (Turbopack) 3.632s (-1.5%) 4.014s (~) 0.382s 8 1.07x
🌐 Redis Next.js (Turbopack) 4.012s (+2.5%) 4.439s (~) 0.427s 7 1.19x
💻 Local Next.js (Turbopack) 7.994s (-3.1%) 8.521s (~) 0.527s 4 2.36x
💻 Local Express 8.724s (-1.4%) 9.274s (~) 0.550s 4 2.58x
💻 Local Nitro 8.916s (+3.5%) 9.521s (+5.5% 🔺) 0.604s 4 2.64x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.651s (+23.8% 🔺) 5.181s (+16.2% 🔺) 1.529s 6 1.00x
▲ Vercel Nitro 3.939s (+18.1% 🔺) 5.480s (+14.2% 🔺) 1.541s 7 1.08x
▲ Vercel Next.js (Turbopack) 4.484s (+18.0% 🔺) 6.171s (+8.4% 🔺) 1.687s 5 1.23x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.546s (-4.8%) 1.004s (-1.7%) 0.458s 60 1.00x
🐘 Postgres Express 0.614s (-27.0% 🟢) 1.007s (~) 0.393s 60 1.13x
🐘 Postgres Next.js (Turbopack) 0.771s (-4.4%) 1.007s (~) 0.237s 60 1.41x
🐘 Postgres Nitro 0.838s (-0.9%) 1.007s (~) 0.169s 60 1.54x
💻 Local Next.js (Turbopack) 0.851s (~) 1.004s (-1.7%) 0.153s 60 1.56x
💻 Local Express 0.976s (-0.7%) 1.076s (-8.9% 🟢) 0.100s 56 1.79x
💻 Local Nitro 1.003s (+1.5%) 1.544s (+30.7% 🔺) 0.541s 39 1.84x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 10.201s (+1.6%) 11.953s (-1.5%) 1.752s 6 1.00x
▲ Vercel Next.js (Turbopack) 10.221s (-10.4% 🟢) 12.283s (-8.5% 🟢) 2.062s 5 1.00x
▲ Vercel Nitro 10.423s (+1.6%) 12.192s (~) 1.769s 5 1.02x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.286s (-0.7%) 2.006s (~) 0.719s 45 1.00x
🐘 Postgres Express 1.454s (-28.0% 🟢) 2.008s (-23.9% 🟢) 0.554s 45 1.13x
🐘 Postgres Next.js (Turbopack) 1.845s (-6.3% 🟢) 2.030s (-10.1% 🟢) 0.186s 45 1.43x
🐘 Postgres Nitro 1.954s (-3.8%) 2.125s (-21.7% 🟢) 0.172s 43 1.52x
💻 Local Next.js (Turbopack) 2.694s (-1.1%) 3.041s (~) 0.347s 30 2.09x
💻 Local Express 2.998s (-1.1%) 3.585s (~) 0.587s 26 2.33x
💻 Local Nitro 3.042s (+0.8%) 3.801s (+5.3% 🔺) 0.759s 24 2.36x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 32.156s (-6.3% 🟢) 34.190s (-5.1% 🟢) 2.034s 3 1.00x
▲ Vercel Express 32.729s (-3.6%) 34.334s (-3.6%) 1.605s 3 1.02x
▲ Vercel Nitro 33.612s (-2.0%) 35.709s (-1.6%) 2.097s 3 1.05x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 2.664s (+1.7%) 3.084s (+0.9%) 0.420s 39 1.00x
🐘 Postgres Express 3.123s (-24.3% 🟢) 3.883s (-20.0% 🟢) 0.760s 31 1.17x
🐘 Postgres Next.js (Turbopack) 3.724s (-5.3% 🟢) 4.011s (-5.7% 🟢) 0.287s 30 1.40x
🐘 Postgres Nitro 4.085s (~) 4.742s (-1.5%) 0.657s 26 1.53x
💻 Local Next.js (Turbopack) 8.693s (~) 9.017s (~) 0.324s 14 3.26x
💻 Local Nitro 8.984s (-1.9%) 9.402s (-3.9%) 0.418s 13 3.37x
💻 Local Express 9.154s (~) 9.865s (-1.5%) 0.710s 13 3.44x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 86.432s (-1.5%) 88.011s (-1.9%) 1.579s 2 1.00x
▲ Vercel Express 89.085s (+3.0%) 90.993s (+2.8%) 1.908s 2 1.03x
▲ Vercel Next.js (Turbopack) 91.203s (-1.0%) 93.905s (~) 2.702s 2 1.06x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.228s (-22.8% 🟢) 1.008s (~) 0.780s 60 1.00x
🌐 Redis Next.js (Turbopack) 0.247s (-7.3% 🟢) 1.004s (~) 0.757s 60 1.08x
🐘 Postgres Next.js (Turbopack) 0.257s (-2.3%) 1.008s (~) 0.751s 60 1.13x
🐘 Postgres Nitro 0.284s (-3.9%) 1.008s (~) 0.724s 60 1.24x
💻 Local Nitro 0.563s (-2.9%) 1.005s (~) 0.442s 60 2.46x
💻 Local Express 0.570s (-6.5% 🟢) 1.004s (-1.7%) 0.434s 60 2.50x
💻 Local Next.js (Turbopack) 0.572s (-1.7%) 1.004s (~) 0.433s 60 2.50x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.676s (-3.8%) 3.336s (+0.6%) 1.661s 18 1.00x
▲ Vercel Nitro 1.737s (+2.9%) 3.538s (+4.1%) 1.801s 17 1.04x
▲ Vercel Next.js (Turbopack) 2.133s (+6.5% 🔺) 4.096s (+11.9% 🔺) 1.962s 15 1.27x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.382s (-27.1% 🟢) 1.007s (~) 0.626s 90 1.00x
🐘 Postgres Next.js (Turbopack) 0.483s (-3.4%) 1.008s (~) 0.525s 90 1.27x
🐘 Postgres Nitro 0.506s (-2.0%) 1.008s (~) 0.502s 90 1.32x
🌐 Redis Next.js (Turbopack) 1.082s (~) 1.942s (+4.3%) 0.860s 47 2.83x
💻 Local Nitro 2.386s (-6.5% 🟢) 3.008s (~) 0.622s 30 6.25x
💻 Local Express 2.547s (+0.6%) 3.009s (~) 0.461s 30 6.67x
💻 Local Next.js (Turbopack) 2.563s (-3.1%) 3.009s (~) 0.446s 30 6.71x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.883s (-8.2% 🟢) 4.386s (-10.1% 🟢) 1.503s 21 1.00x
▲ Vercel Nitro 3.052s (-24.9% 🟢) 4.558s (-19.3% 🟢) 1.507s 20 1.06x
▲ Vercel Next.js (Turbopack) 3.621s (-93.5% 🟢) 5.067s (-91.2% 🟢) 1.446s 18 1.26x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.589s (-28.3% 🟢) 1.008s (~) 0.419s 120 1.00x
🐘 Postgres Next.js (Turbopack) 0.761s (-0.7%) 1.008s (~) 0.247s 120 1.29x
🐘 Postgres Nitro 0.794s (-1.0%) 1.009s (~) 0.215s 119 1.35x
🌐 Redis Next.js (Turbopack) 2.603s (~) 3.007s (~) 0.404s 40 4.42x
💻 Local Next.js (Turbopack) 10.694s (-2.0%) 11.299s (-1.6%) 0.604s 11 18.16x
💻 Local Nitro 10.778s (-3.6%) 11.118s (-5.4% 🟢) 0.341s 11 18.30x
💻 Local Express 11.290s (~) 11.940s (+0.8%) 0.650s 11 19.17x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 7.941s (+2.1%) 9.735s (+4.4%) 1.794s 13 1.00x
▲ Vercel Nitro 8.419s (+1.8%) 10.317s (+2.0%) 1.898s 12 1.06x
▲ Vercel Next.js (Turbopack) 38.181s (+383.8% 🔺) 39.845s (+314.0% 🔺) 1.664s 10 4.81x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.162s (-26.4% 🟢) 0.999s (+0.8%) 0.001s (-21.4% 🟢) 1.011s (~) 0.849s 10 1.00x
💻 Local Next.js (Turbopack) 0.187s (+6.8% 🔺) 1.002s (~) 0.011s (-1.7%) 1.017s (~) 0.830s 10 1.15x
🐘 Postgres Next.js (Turbopack) 0.192s (-3.7%) 0.999s (~) 0.001s (+16.7% 🔺) 1.011s (~) 0.820s 10 1.18x
💻 Local Nitro 0.203s (~) 1.003s (~) 0.012s (+8.3% 🔺) 1.018s (~) 0.815s 10 1.25x
💻 Local Express 0.203s (-2.2%) 1.003s (~) 0.011s (-6.6% 🟢) 1.017s (~) 0.815s 10 1.25x
🐘 Postgres Nitro 0.207s (-2.6%) 0.995s (~) 0.001s (~) 1.013s (~) 0.806s 10 1.27x
🌐 Redis Next.js (Turbopack) 0.214s (+51.9% 🔺) 1.000s (~) 0.041s (+3061.5% 🔺) 1.047s (+4.0%) 0.833s 10 1.32x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.587s (-8.6% 🟢) 2.922s (-6.2% 🟢) 0.394s (-38.4% 🟢) 3.921s (-14.2% 🟢) 2.333s 10 1.00x
▲ Vercel Nitro 1.685s (+1.6%) 2.716s (-5.4% 🟢) 0.506s (-9.6% 🟢) 3.869s (-5.7% 🟢) 2.184s 10 1.06x
▲ Vercel Next.js (Turbopack) 1.905s (+14.3% 🔺) 3.138s (+10.2% 🔺) 0.444s (-28.7% 🟢) 4.307s (+3.5%) 2.402s 10 1.20x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.386s (+1.7%) 1.000s (~) 0.005s (+65.2% 🔺) 1.013s (~) 0.627s 60 1.00x
🐘 Postgres Express 0.511s (-17.9% 🟢) 1.004s (~) 0.004s (-9.4% 🟢) 1.024s (~) 0.513s 59 1.32x
🐘 Postgres Nitro 0.602s (-3.5%) 1.005s (~) 0.004s (-6.1% 🟢) 1.024s (~) 0.422s 59 1.56x
🐘 Postgres Next.js (Turbopack) 0.604s (-1.3%) 1.008s (~) 0.006s (+47.8% 🔺) 1.025s (~) 0.421s 59 1.56x
💻 Local Next.js (Turbopack) 0.659s (-2.4%) 1.008s (-1.7%) 0.009s (-3.2%) 1.023s (-1.6%) 0.364s 59 1.71x
💻 Local Nitro 0.699s (-4.0%) 1.027s (+1.8%) 0.009s (+1.3%) 1.039s (+1.7%) 0.339s 58 1.81x
💻 Local Express 0.724s (-0.5%) 1.009s (~) 0.009s (+9.2% 🔺) 1.023s (~) 0.299s 59 1.88x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.935s (-6.6% 🟢) 5.372s (-2.2%) 0.338s (-31.6% 🟢) 6.265s (-5.5% 🟢) 2.331s 10 1.00x
▲ Vercel Nitro 4.257s (+3.0%) 5.872s (+10.1% 🔺) 0.175s (-25.7% 🟢) 6.669s (+6.2% 🔺) 2.412s 10 1.08x
▲ Vercel Next.js (Turbopack) 4.842s (-20.5% 🟢) 6.227s (-18.8% 🟢) 0.228s (~) 7.227s (-16.2% 🟢) 2.385s 9 1.23x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.839s (~) 1.000s (~) 0.000s (-100.0% 🟢) 1.004s (~) 0.165s 60 1.00x
🐘 Postgres Express 0.917s (-5.3% 🟢) 1.051s (-14.1% 🟢) 0.000s (+71.4% 🔺) 1.073s (-14.3% 🟢) 0.156s 56 1.09x
🐘 Postgres Next.js (Turbopack) 0.945s (-3.6%) 1.229s (-5.8% 🟢) 0.000s (-100.0% 🟢) 1.257s (-4.3%) 0.313s 48 1.13x
🐘 Postgres Nitro 1.000s (~) 1.242s (-8.3% 🟢) 0.000s (-100.0% 🟢) 1.265s (-8.3% 🟢) 0.265s 48 1.19x
💻 Local Nitro 1.173s (-6.0% 🟢) 2.019s (~) 0.000s (~) 2.022s (~) 0.849s 30 1.40x
💻 Local Express 1.246s (~) 2.021s (~) 0.000s (+42.9% 🔺) 2.024s (~) 0.779s 30 1.48x
💻 Local Next.js (Turbopack) 1.304s (+0.9%) 2.020s (~) 0.001s (+87.5% 🔺) 2.024s (~) 0.720s 30 1.55x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.128s (-6.8% 🟢) 4.195s (-2.3%) 0.000s (+Infinity% 🔺) 4.821s (-3.7%) 1.693s 14 1.00x
▲ Vercel Next.js (Turbopack) 3.287s (+4.1%) 4.582s (+6.5% 🔺) 0.000s (-100.0% 🟢) 5.263s (+6.5% 🔺) 1.976s 12 1.05x
▲ Vercel Express 3.349s (-5.4% 🟢) 4.234s (-6.0% 🟢) 0.000s (-93.8% 🟢) 4.800s (-7.8% 🟢) 1.451s 13 1.07x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.547s (~) 2.002s (-1.6%) 0.000s (+200.0% 🔺) 2.006s (-1.6%) 0.459s 30 1.00x
🐘 Postgres Express 1.761s (-0.9%) 2.185s (+3.1%) 0.000s (NaN%) 2.226s (+2.8%) 0.466s 27 1.14x
🐘 Postgres Nitro 1.800s (~) 2.139s (~) 0.000s (-50.0% 🟢) 2.154s (~) 0.354s 28 1.16x
🐘 Postgres Next.js (Turbopack) 1.851s (-3.4%) 2.071s (-3.4%) 0.000s (-3.4%) 2.093s (-2.8%) 0.242s 29 1.20x
💻 Local Nitro 3.314s (-8.2% 🟢) 3.968s (-3.2%) 0.000s (-47.9% 🟢) 3.971s (-3.2%) 0.657s 16 2.14x
💻 Local Express 3.627s (+4.7%) 4.166s (+3.3%) 0.000s (-50.0% 🟢) 4.170s (+3.3%) 0.543s 15 2.34x
💻 Local Next.js (Turbopack) 3.796s (+2.7%) 4.299s (+3.2%) 0.001s (+33.3% 🔺) 4.304s (+3.2%) 0.509s 15 2.45x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.898s (-11.5% 🟢) 5.003s (-9.0% 🟢) 0.000s (NaN%) 5.555s (-9.9% 🟢) 1.656s 11 1.00x
▲ Vercel Next.js (Turbopack) 4.279s (-10.4% 🟢) 5.435s (-7.8% 🟢) 0.000s (-50.0% 🟢) 6.134s (-7.2% 🟢) 1.854s 10 1.10x
▲ Vercel Nitro 4.303s (-6.3% 🟢) 5.224s (-7.1% 🟢) 0.000s (+81.8% 🔺) 5.902s (-6.3% 🟢) 1.598s 11 1.10x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 15/21
🐘 Postgres Express 21/21
▲ Vercel Express 14/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 18/21
Next.js (Turbopack) 🌐 Redis 12/21
Nitro 🐘 Postgres 17/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds backward-compatible hook/webhook resume behavior by disabling payload encryption when the target workflow run was created by an older @workflow/core deployment that can’t decode the encr serialization format.

Changes:

  • Introduces getRunCapabilities() (version-based capabilities) and uses it in resumeHook() to suppress encryption for pre-4.2.0-beta.64 runs.
  • Adds unit tests for capability detection across version ranges.
  • Adds semver to the pnpm catalog and updates workspace packages/lockfile to consume it via catalog:.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Adds semver to the shared version catalog.
pnpm-lock.yaml Updates lockfile for semver and related type deps/catalog usage.
packages/next/package.json Switches semver dependency to catalog:.
packages/core/package.json Adds semver (+ @types/semver) to support runtime capability checks.
packages/core/src/runtime/resume-hook.ts Checks target run capabilities before encrypting dehydrated hook payloads.
packages/core/src/capabilities.ts Implements capability lookup based on workflowCoreVersion.
packages/core/src/capabilities.test.ts Tests version threshold behavior for encryption support.
.changeset/fix-hook-resume-encryption-compat.md Publishes a patch changeset documenting the compatibility fix.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +131 to +136
const capabilities = getRunCapabilities(
workflowRun.executionContext?.workflowCoreVersion
);
if (!capabilities.supportsEncryption) {
encryptionKey = undefined;
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflowRun.executionContext is typed as Record<string, any> | undefined (from @workflow/world), so workflowCoreVersion may be non-string. Passing a non-string (or malformed string) into getRunCapabilities() risks a runtime throw inside semver comparison. Consider normalizing here (e.g., only pass through when it’s a string) so resumeHook never fails due to unexpected executionContext contents.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +44
return {
supportsEncryption: semver.gte(workflowCoreVersion, ENCRYPTION_MIN_VERSION),
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

semver.gte(workflowCoreVersion, ENCRYPTION_MIN_VERSION) will throw if workflowCoreVersion is not a valid semver string. Since this value ultimately comes from stored run metadata (and may be missing/malformed/user-supplied), it would be safer to treat invalid versions as “no encryption” instead of throwing (e.g., validate with semver.valid / semver.clean, or wrap the comparison in a try/catch and return supportsEncryption: false on errors).

Suggested change
return {
supportsEncryption: semver.gte(workflowCoreVersion, ENCRYPTION_MIN_VERSION),
const validVersion = semver.valid(workflowCoreVersion);
if (!validVersion) {
return { supportsEncryption: false };
}
return {
supportsEncryption: semver.gte(validVersion, ENCRYPTION_MIN_VERSION),

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +14
describe('getRunCapabilities', () => {
it('returns no encryption for undefined version', () => {
const caps = getRunCapabilities(undefined);
expect(caps.supportsEncryption).toBe(false);
});

it('returns no encryption for pre-encryption versions', () => {
expect(getRunCapabilities('4.1.0-beta.63').supportsEncryption).toBe(false);
expect(getRunCapabilities('4.0.1-beta.27').supportsEncryption).toBe(false);
expect(getRunCapabilities('3.0.0').supportsEncryption).toBe(false);
});
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests cover expected version ordering, but they don’t assert behavior for malformed/unknown workflowCoreVersion values (e.g. 'dev', 'v4.2.0-beta.64', or other non-semver strings). Given the runtime value originates from persisted metadata, please add a test that getRunCapabilities() returns supportsEncryption=false and does not throw for invalid version strings.

Copilot uses AI. Check for mistakes.
When resumeHook()/resumeWebhook() is called on a newer deployment that
supports encryption, it would encode the payload with the 'encr' format.
If the target workflow run was created by an older deployment that
predates encryption support, the run would fail with:
  Error: Unknown serialization format: "encr". Known formats: devl

Add a capabilities table that maps @workflow/core versions to supported
serialization formats. Before encoding, resumeHook() now checks the
target run's workflowCoreVersion and suppresses encryption when the
run's deployment doesn't support it.
Copy link
Copy Markdown
Collaborator

@pranaygp pranaygp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. The compatibility gate in resumeHook() matches the rollout boundary for encrypted serialization and the added capability tests cover the key version cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants